# -*- coding: utf-8 -*-

import sys
import httplib
from urllib import urlencode
from urllib2 import urlopen
from copy import copy
import logging
from HTMLParser import HTMLParser, HTMLParseError

import sumc


l = logging.getLogger("sumc.client")
l.addHandler(sumc._NullHandler())


def dictFromTuples(tuples, *keys):
    d = {}
    for k, v in tuples:
        if k in keys:
            d[k] = v
    for k in keys:
        if not d.has_key(k):
            d[k] = None
    return d

class Extractor(HTMLParser):
    """ Populates its hiddens, selects and routes properties with data from the page
    
    The hiddens property will contain "name": "value" for every 
    <input type="hidden" ... /> element.
    
    The selects will contain a 2-dimensional dict. The first is indexed by the
    name of the <select ...>. This will contain a dict with "value": "text" pairs
    of the <option ...> elements.
    
    The routes contain "name": "value" for every <input type="radio" ...> element
    """
    INITIAL = 'initial'
    SELECT = 'select'
    OPTION = 'option'
    LABEL = 'label'
    
    def reset(self):
        HTMLParser.reset(self)
        self._state = self.INITIAL 
        self.hiddens = {}
        self.selects = {}
        self.routes = []
        self._currentRouteId = None
        
    def process(self, data):
        self.reset()
        self.feed(data)
        return self
    
    def handle_starttag(self, tag, attrs):
        if tag == 'input':
            self._handleInput(attrs)
        if tag == 'select':
            self._handleSelect(attrs)
        if tag == 'option':
            self._handleOption(attrs)
        if tag == 'label':
            self._handleLabel(attrs)
        
    def handle_endtag(self, tag):
        if tag == 'select':
            self._handleSelectEnd()
        if tag == 'option':
            self._handleOptionEnd()
        if tag == 'label':
            self._handleLabelEnd()
        
    def handle_data(self, data):
        if self._state == self.OPTION:
            self._currentOptionText += data
        if self._state == self.LABEL:
            self._currentRouteLabel += data
        
    def _handleSelect(self, attrs):
        self._state = self.SELECT
        d = dictFromTuples(attrs, 'name')
        self._currentSelectName = d['name']
        self._currentSelectOptions = []
        
    def _handleSelectEnd(self):
        self.selects[self._currentSelectName] = self._currentSelectOptions
        self._state = self.INITIAL

    def _handleLabel(self, attrs):
        if self._currentRouteId:
            self._state = self.LABEL
            self._currentRouteLabel = ''
    
    def _handleLabelEnd(self):
        if self._currentRouteId:
            self.routes.append((self._currentRouteId, self._currentRouteLabel))
            self._currentRouteId = None
            self._state = self.INITIAL
        
    def _handleOption(self, attrs):
        if self._state != self.SELECT:
            return
        self._state = self.OPTION
        d = dictFromTuples(attrs, 'value')
        self._currentOptionValue = d['value']
        self._currentOptionText = ''
        
    def _handleOptionEnd(self):
        self._currentSelectOptions.append(
            (self._currentOptionValue, self._currentOptionText))
            
        self._state = self.SELECT
        
    def _handleInput(self, attrs):
        value = ''
        name = None
        itype = None
        for n, v in attrs:
            if n.lower() == 'value':
                value = v
            elif n.lower() == 'type':
                itype = v
            elif n.lower() == 'name':
                name = v
        if itype and itype.lower() == 'hidden':
            self.hiddens[name] = value
        if itype and itype.lower() == 'radio':
            self._currentRouteId = value


class ArrivalTimesExtractor(HTMLParser):
    """Will extract arrival times from a page with results.
    
    The lines are stored as a list of UTF-8 encoded strings in .results
    """
    def __init__(self):
        self.reset()
        self.results = []
        self._currentResult = None
        self._inResult = False
        
    def handle_starttag(self, tag, attrs):
        if tag == 'span':
            d = dictFromTuples(attrs, 'id')
            if d['id'].find('ctl00_ContentPlaceHolder1_gvTimes_ctl') == 0:
                self._inResult = True
                self._currentResult = ''
    
    def handle_data(self, data):
        if self._inResult:
            self._currentResult += data
    
    def handle_endtag(self, tag):
        if tag == 'span' and self._inResult:
            self.results.append(self._currentResult)
            self._currentResult = None
            self._inResult = False
        
class ClientBase(object):
    
    def __init__(self):
        self._parser = Extractor()
        self._params = self._defaultParams.copy()
        
    def _go(self):
        if not self._params.has_key('__VIEWSTATE'):
            res = urlopen(self._url)
            self._parser.process(res.read())
            self._params.update(**self._parser.hiddens)
            
        l.debug("_go(): posting %s", str(self._params))
        res = urlopen(self._url, urlencode(self._params))
        self._data = data = res.read()
        self._parser.process(data)
        self._params.update(**self._parser.hiddens)
        
    def _normalize(self, tpls):
        return [(int(k), v.strip().decode('utf-8')) 
                                        for (k,v) in tpls if k != '']
            
    
class ClientByLine(ClientBase):
    
    _defaultParams = {
        'ctl00$ContentPlaceHolder1$ddlTransportType': '',
        'ctl00$ContentPlaceHolder1$ddlLines': '',
        'ctl00$ContentPlaceHolder1$rblRoute': '',
        'ctl00$ContentPlaceHolder1$ddlStops': '',
    }
    
    _url = 'http://gps.skgt-bg.com/Web/SelectByLine.aspx'
    
    def setType(self, ttype):
        if self._params['ctl00$ContentPlaceHolder1$ddlTransportType'] != ttype:
            self._params['ctl00$ContentPlaceHolder1$ddlTransportType'] = ttype
            self._params['ctl00$ContentPlaceHolder1$ddlLines'] = ''
            self._params['ctl00$ContentPlaceHolder1$rblRoute'] = ''
            self._params['ctl00$ContentPlaceHolder1$ddlStops'] = ''
            l.info("setType(): type set to %s, others cleared", ttype)
            self._go()
            
    def setLine(self, line):
        # XXX: does not check for ttype!
        if self._params['ctl00$ContentPlaceHolder1$ddlLines'] != line:
            self._params['ctl00$ContentPlaceHolder1$ddlLines'] = line
            self._params['ctl00$ContentPlaceHolder1$rblRoute'] = ''
            self._params['ctl00$ContentPlaceHolder1$ddlStops'] = ''
            l.info("setLine(): %s, route and stops cleared", line)
            self._go()

    def setRoute(self, route):
        if self._params['ctl00$ContentPlaceHolder1$rblRoute'] != route:
            self._params['ctl00$ContentPlaceHolder1$rblRoute'] = route
            self._params['ctl00$ContentPlaceHolder1$ddlStops'] = ''
            l.info("setRoute(): %s", route)
            self._go()
            
    def getLines(self, ttype):
        self.setType(ttype)
        return self._normalize(self._parser.selects['ctl00$ContentPlaceHolder1$ddlLines'])
    
    def getLineRoutes(self, ttype, line):
        self.setType(ttype)
        self.setLine(line)
        return self._normalize(self._parser.routes)
        
    def getRouteStops(self, ttype, line, route):
        self.setType(ttype)
        self.setLine(line)
        self.setRoute(route)
        return self._normalize(self._parser.selects['ctl00$ContentPlaceHolder1$ddlStops'])
    
    def getArrivalTimes(self, ttype, line, route, stop):
        self.setType(ttype)
        self.setLine(line)
        self.setRoute(route)
        self._params['ctl00$ContentPlaceHolder1$ddlStops'] = stop
        self._go()
        p = ArrivalTimesExtractor()
        p.feed(self._data)
        return p.results
        
        
class ClientByStop(ClientBase):
    
    _defaultParams = {
        'ctl00$ContentPlaceHolder1$tbStopCode': '',
        'ctl00$ContentPlaceHolder1$ddlLine': '',
        'ctl00$ContentPlaceHolder1$hfStopID': '',
        'ctl00$ContentPlaceHolder1$btnSearchLine.x': 7,
        'ctl00$ContentPlaceHolder1$btnSearchLine.y': 7,
    }
    
    _url = 'http://gps.skgt-bg.com/Web/SelectByStop.aspx'
    
    def setStopCode(self, stopCode):
        if self._params['ctl00$ContentPlaceHolder1$tbStopCode'] != stopCode:
            self._params['ctl00$ContentPlaceHolder1$tbStopCode'] = stopCode
            self._params['ctl00$ContentPlaceHolder1$ddlLine'] = ''
            self._go()
            
    def setLine(self, line):
        if self._params['ctl00$ContentPlaceHolder1$ddlLine'] != line:
            self._params['ctl00$ContentPlaceHolder1$ddlLine'] = line
            del self._params['ctl00$ContentPlaceHolder1$btnSearchLine.x']
            del self._params['ctl00$ContentPlaceHolder1$btnSearchLine.y']
            self._params['ctl00$ContentPlaceHolder1$btnRefresh.x'] = 5
            self._params['ctl00$ContentPlaceHolder1$btnRefresh.y'] = 5
            
            self._go()
            
    def getLinesForStop(self, stopCode):
        self.setStopCode(stopCode)
        return self._normalize(self._parser.selects['ctl00$ContentPlaceHolder1$ddlLine'])
        
    def getResults(self, stopCode, lineId):
        self.setStopCode(stopCode)
        self.setLine(lineId)
        
        p = ArrivalTimesExtractor()
        p.feed(self._data)
        return p.results
        

if __name__ == '__main__':
    import logging as l
    l.basicConfig(stream=sys.stdout,
              format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
              level=l.DEBUG)

    c = ClientByStop()
    print c.getLinesForStop(1998)
    print c.getResults(1998, 51)
#    print c._data

#    c = SumcClient()
#    print c.getLines(sumc.BUS)
